// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WorkSchedulerProvider.cs" company="AzureContrib (Open source library http://azurecontrib.codeplex.com)">
//   Microsoft Public License (Ms-PL)
// </copyright>
// <summary>
//   A standard implementation of the <see cref="IWorkSchedulerProvider" />.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;

using AzureContrib.WindowsAzure.Diagnostics;
using AzureContrib.WindowsAzure.Properties;
using AzureContrib.WindowsAzure.ServiceRuntime;

namespace AzureContrib.WindowsAzure.WorkServices
{
	/// <summary>
	/// A standard implementation of the <see cref="IWorkSchedulerProvider"/>.
	/// </summary>
	internal class WorkSchedulerProvider : IWorkSchedulerProvider
	{
		private IWorkScheduler currentWorkScheduler;

		/// <summary>
		/// Initializes a new instance of the <see cref="WorkSchedulerProvider"/> class. 
		/// Construct a <see cref="IWorkSchedulerProvider"/>
		/// </summary>
		/// <param name="traceFactory">
		/// Creates the <see cref="ITrace"/> we trace to.
		/// </param>
		[ImportingConstructor]
		public WorkSchedulerProvider(ITraceFactory traceFactory)
		{
			Trace = traceFactory.Create(GetType());
			CurrentWorkSchedulerName = WorkSchedulerNames.DefaultWorkSchedulerName;
			AvailableWorkSchedulers = Enumerable.Empty<Lazy<IWorkScheduler, IWorkSchedulerMetadata>>();
		}

		/// <summary>
		/// Gets the name of the configuration setting for the name of the current work scheduler to use.
		/// </summary>
		public string CurrentWorkSchedulerName { get; private set; }

		/// <summary>
		/// Gets or sets the current <see cref="IRoleEnvironment"/>
		/// </summary>
		[Import]
		public IRoleEnvironment RoleEnvironment { get; set; }

		/// <summary>
		/// Gets or sets the current <see cref="ITrace"/>
		/// </summary>
		public ITrace Trace { get; set; }

		/// <summary>
		/// Gets or sets all <see cref="IWorkScheduler"/> implementations available (as <see cref="Lazy{T,TMetadata}"/>)
		/// </summary>
		[ImportMany]
		public IEnumerable<Lazy<IWorkScheduler, IWorkSchedulerMetadata>> AvailableWorkSchedulers { get; set; }

		/// <summary>
		/// Get the <see cref="IWorkScheduler"/> currently in use.
		/// </summary>
		/// <returns>
		/// The get current work scheduler.
		/// </returns>
		public IWorkScheduler CurrentWorkScheduler()
		{
			if (currentWorkScheduler != null)
			{
				return currentWorkScheduler;
			}

			var configuredSchedulerName = RoleEnvironment.GetConfigurationSettingValue(WorkSchedulerNames.CurrentWorkSchedulerConfigSettingName);

			Lazy<IWorkScheduler, IWorkSchedulerMetadata> lazyWorkScheduler;

			if (!string.IsNullOrEmpty(configuredSchedulerName))
			{
				if (!TryFindWorkScheduler(configuredSchedulerName, out lazyWorkScheduler))
				{
					Trace.Warning(Strings.ConfiguredWorkItemSchedulerUnavailable, configuredSchedulerName);
				}
				else
				{
					CurrentWorkSchedulerName = configuredSchedulerName;
					SetCurrentWorkScheduler(lazyWorkScheduler);
					return currentWorkScheduler;
				}
			}

			if (!TryFindWorkScheduler(CurrentWorkSchedulerName, out lazyWorkScheduler))
			{
				Trace.Error(Strings.NoWorkItemSchedulerAvailable, CurrentWorkSchedulerName);
				return null;
			}

			SetCurrentWorkScheduler(lazyWorkScheduler);
			return currentWorkScheduler;
		}

		private void SetCurrentWorkScheduler(Lazy<IWorkScheduler, IWorkSchedulerMetadata> lazyWorkScheduler)
		{
			currentWorkScheduler = lazyWorkScheduler.Value;
			Trace.Information(Strings.WorkSchedulerFound, lazyWorkScheduler.Metadata.Name, currentWorkScheduler.GetType());
		}

		private bool TryFindWorkScheduler(string workSchedulerName, out Lazy<IWorkScheduler, IWorkSchedulerMetadata> workScheduler)
		{
			workScheduler = null;

			try
			{
				if (!string.IsNullOrEmpty(workSchedulerName))
				{
					workScheduler = AvailableWorkSchedulers.Where(ws => ws.Metadata.Name == workSchedulerName)
																								 .SingleOrDefault();
				}
			}
			catch (InvalidOperationException ioe)
			{
				Trace.Error(Strings.WorkSchedulerNotSelectable, workSchedulerName, ioe.Message);
				workScheduler = null;
			}

			return workScheduler != null;
		}
	}
}